Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include "../../global.hpp"
00022
00023 #include "ai.hpp"
00024 #include "../actions.hpp"
00025 #include "../manager.hpp"
00026
00027 #include "../../attack_prediction.hpp"
00028 #include "foreach.hpp"
00029 #include "../../game_config.hpp"
00030 #include "../../log.hpp"
00031 #include "../../map.hpp"
00032 #include "../../team.hpp"
00033
00034 static lg::log_domain log_ai("ai/attack");
00035 #define LOG_AI LOG_STREAM(info, log_ai)
00036 #define ERR_AI LOG_STREAM(err, log_ai)
00037
00038 namespace ai {
00039
00040 void attack_analysis::analyze(const gamemap& map, unit_map& units,
00041 const readonly_context& ai_obj,
00042 const move_map& dstsrc, const move_map& srcdst,
00043 const move_map& enemy_dstsrc, double aggression)
00044 {
00045 const unit_map::const_iterator defend_it = units.find(target);
00046 assert(defend_it != units.end());
00047
00048
00049 map_location adj[6];
00050 get_adjacent_tiles(target,adj);
00051 size_t tile;
00052 for(tile = 0; tile != 6; ++tile) {
00053 const unit_map::const_iterator leader = units.find(adj[tile]);
00054 if(leader != units.end() && leader->can_recruit() && !ai_obj.current_team().is_enemy(leader->side())) {
00055 break;
00056 }
00057 }
00058
00059 leader_threat = (tile != 6);
00060 uses_leader = false;
00061
00062 target_value = defend_it->cost();
00063 target_value += (double(defend_it->experience())/
00064 double(defend_it->max_experience()))*target_value;
00065 target_starting_damage = defend_it->max_hitpoints() -
00066 defend_it->hitpoints();
00067
00068
00069
00070
00071
00072 alternative_terrain_quality = 0.0;
00073 double cost_sum = 0.0;
00074 for(size_t i = 0; i != movements.size(); ++i) {
00075 const unit_map::const_iterator att = units.find(movements[i].first);
00076 const double cost = att->cost();
00077 cost_sum += cost;
00078 alternative_terrain_quality += cost*ai_obj.best_defensive_position(movements[i].first,dstsrc,srcdst,enemy_dstsrc).chance_to_hit;
00079 }
00080 alternative_terrain_quality /= cost_sum*100;
00081
00082 avg_damage_inflicted = 0.0;
00083 avg_damage_taken = 0.0;
00084 resources_used = 0.0;
00085 terrain_quality = 0.0;
00086 avg_losses = 0.0;
00087 chance_to_kill = 0.0;
00088
00089 double def_avg_experience = 0.0;
00090 double first_chance_kill = 0.0;
00091
00092 double prob_dead_already = 0.0;
00093 assert(!movements.empty());
00094 std::vector<std::pair<map_location,map_location> >::const_iterator m;
00095
00096 battle_context *prev_bc = NULL;
00097 const combatant *prev_def = NULL;
00098
00099 for (m = movements.begin(); m != movements.end(); ++m) {
00100
00101 unit *up = units.extract(m->first);
00102 up->set_location(m->second);
00103 units.insert(up);
00104 double m_aggression = aggression;
00105
00106 if (up->can_recruit()) {
00107 uses_leader = true;
00108
00109 leader_threat = false;
00110 m_aggression = ai_obj.get_leader_aggression();
00111 }
00112
00113 bool from_cache = false;
00114 battle_context *bc;
00115
00116
00117
00118 std::map<std::pair<map_location, const unit_type *>,std::pair<battle_context_unit_stats,battle_context_unit_stats> >::iterator usc;
00119 const unit_type *up_type = up->type();
00120 if(up_type) {
00121 usc = ai_obj.unit_stats_cache().find(std::pair<map_location, const unit_type *>(target, up_type));
00122 } else {
00123 usc = ai_obj.unit_stats_cache().end();
00124 }
00125
00126 if (usc != ai_obj.unit_stats_cache().end() &&
00127 usc->second.first.attack_num <
00128 static_cast<int>(up->attacks().size())) {
00129
00130 from_cache = true;
00131 bc = new battle_context(usc->second.first, usc->second.second);
00132 } else {
00133 bc = new battle_context(units, m->second, target, -1, -1, m_aggression, prev_def);
00134 }
00135 const combatant &att = bc->get_attacker_combatant(prev_def);
00136 const combatant &def = bc->get_defender_combatant(prev_def);
00137
00138 delete prev_bc;
00139 prev_bc = bc;
00140 prev_def = &bc->get_defender_combatant(prev_def);
00141
00142 if (!from_cache && up_type) {
00143 ai_obj.unit_stats_cache().insert(std::pair<std::pair<map_location, const unit_type *>,std::pair<battle_context_unit_stats,battle_context_unit_stats> >
00144 (std::pair<map_location, const unit_type *>(target, up_type),
00145 std::pair<battle_context_unit_stats,battle_context_unit_stats>(bc->get_attacker_stats(),
00146 bc->get_defender_stats())));
00147 }
00148
00149
00150 double prob_fought = (1.0 - prob_dead_already);
00151
00152
00153 double prob_killed = def.hp_dist[0] - prob_dead_already;
00154 prob_dead_already = def.hp_dist[0];
00155
00156 double prob_died = att.hp_dist[0];
00157 double prob_survived = (1.0 - prob_died) * prob_fought;
00158
00159 double cost = up->cost();
00160 const bool on_village = map.is_village(m->second);
00161
00162 cost += (double(up->experience()) / up->max_experience())*cost;
00163 resources_used += cost;
00164 avg_losses += cost * prob_died;
00165
00166
00167 avg_losses += cost * up->get_state(unit::STATE_POISONED) /2;
00168
00169
00170 if (on_village) {
00171 avg_damage_taken -= game_config::poison_amount*2 * prob_survived;
00172 }
00173
00174 terrain_quality += (double(bc->get_defender_stats().chance_to_hit)/100.0)*cost * (on_village ? 0.5 : 1.0);
00175
00176 double advance_prob = 0.0;
00177
00178 if (!up->advances_to().empty()) {
00179 int xp_for_advance = up->max_experience() - up->experience();
00180
00181
00182
00183 if (xp_for_advance <= 0)
00184 xp_for_advance = 1;
00185
00186 int fight_xp = defend_it->level();
00187 int kill_xp = game_config::kill_xp(fight_xp);
00188
00189 if (fight_xp >= xp_for_advance) {
00190 advance_prob = prob_fought;
00191 avg_losses -= up->cost() * prob_fought;
00192 } else if (kill_xp >= xp_for_advance) {
00193 advance_prob = prob_killed;
00194 avg_losses -= up->cost() * prob_killed;
00195
00196
00197
00198
00199
00200
00201 avg_losses -= up->cost() * 0.25 *
00202 fight_xp * (prob_fought - prob_killed)
00203 / xp_for_advance;
00204 } else {
00205 avg_losses -= up->cost() * 0.25 *
00206 (kill_xp * prob_killed + fight_xp * (prob_fought - prob_killed))
00207 / xp_for_advance;
00208 }
00209
00210
00211
00212 if (bc->get_attacker_stats().plagues) {
00213 avg_losses -= prob_killed * up->cost();
00214 }
00215 }
00216
00217
00218 avg_damage_taken += (up->hitpoints() - att.average_hp()) * (1.0 - advance_prob);
00219
00220
00221
00222
00223
00224
00225 int fight_xp = up->level();
00226 int kill_xp = game_config::kill_xp(fight_xp);
00227 def_avg_experience += fight_xp * (1.0 - att.hp_dist[0]) + kill_xp * att.hp_dist[0];
00228 if (m == movements.begin()) {
00229 first_chance_kill = def.hp_dist[0];
00230 }
00231 }
00232
00233 if (!defend_it->advances_to().empty() &&
00234 def_avg_experience >= defend_it->max_experience() - defend_it->experience()) {
00235
00236 chance_to_kill = first_chance_kill;
00237
00238 avg_damage_inflicted = defend_it->hitpoints() - defend_it->max_hitpoints();
00239 } else {
00240 chance_to_kill = prev_def->hp_dist[0];
00241 avg_damage_inflicted = defend_it->hitpoints() - prev_def->average_hp(map.gives_healing(defend_it->get_location()));
00242 }
00243
00244 delete prev_bc;
00245 terrain_quality /= resources_used;
00246
00247
00248 for (m = movements.begin(); m != movements.end(); ++m) {
00249 units.move(m->second, m->first);
00250 }
00251 }
00252
00253 bool attack_analysis::attack_close(const map_location& loc) const
00254 {
00255 std::set<map_location> &attacks = manager::get_ai_info().recent_attacks;
00256 for(std::set<map_location>::const_iterator i = attacks.begin(); i != attacks.end(); ++i) {
00257 if(distance_between(*i,loc) < 4) {
00258 return true;
00259 }
00260 }
00261
00262 return false;
00263 }
00264
00265
00266 double attack_analysis::rating(double aggression, const readonly_context& ai_obj) const
00267 {
00268 if(leader_threat) {
00269 aggression = 1.0;
00270 }
00271
00272 if(uses_leader) {
00273 aggression = ai_obj.get_leader_aggression();
00274 }
00275
00276 double value = chance_to_kill*target_value - avg_losses*(1.0-aggression);
00277
00278 if(terrain_quality > alternative_terrain_quality) {
00279
00280
00281
00282
00283
00284 #ifdef SUOKKO
00285
00286 const double exposure_mod = uses_leader ? ai_obj.current_team().caution()* 8.0 : ai_obj.current_team().caution() * 4.0;
00287 const double exposure = exposure_mod*resources_used*((terrain_quality - alternative_terrain_quality)/10)*vulnerability/std::max<double>(0.01,support);
00288 #else
00289 const double exposure_mod = uses_leader ? 2.0 : ai_obj.get_caution();
00290 const double exposure = exposure_mod*resources_used*(terrain_quality - alternative_terrain_quality)*vulnerability/std::max<double>(0.01,support);
00291 #endif
00292 LOG_AI << "attack option has base value " << value << " with exposure " << exposure << ": "
00293 << vulnerability << "/" << support << " = " << (vulnerability/std::max<double>(support,0.1)) << "\n";
00294 value -= exposure*(1.0-aggression);
00295 }
00296
00297
00298 value += ((target_starting_damage/3 + avg_damage_inflicted) - (1.0-aggression)*avg_damage_taken)/10.0;
00299
00300
00301
00302
00303 if(!is_surrounded || (support != 0 && avg_damage_taken != 0))
00304 {
00305
00306
00307
00308 if(vulnerability > 50.0 && vulnerability > support*2.0
00309 && chance_to_kill < 0.02 && aggression < 0.75
00310 && !attack_close(target)) {
00311 return -1.0;
00312 }
00313 }
00314
00315 if(!leader_threat && vulnerability*terrain_quality > 0.0) {
00316 value *= support/(vulnerability*terrain_quality);
00317 }
00318
00319 value /= ((resources_used/2) + (resources_used/2)*terrain_quality);
00320
00321 if(leader_threat) {
00322 value *= 5.0;
00323 }
00324
00325 LOG_AI << "attack on " << target << ": attackers: " << movements.size()
00326 << " value: " << value << " chance to kill: " << chance_to_kill
00327 << " damage inflicted: " << avg_damage_inflicted
00328 << " damage taken: " << avg_damage_taken
00329 << " vulnerability: " << vulnerability
00330 << " support: " << support
00331 << " quality: " << terrain_quality
00332 << " alternative quality: " << alternative_terrain_quality << "\n";
00333
00334 return value;
00335 }
00336
00337 }