The Battle for Wesnoth  1.13.4+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
recruitment.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2013 - 2016 by Felix Bauer
3  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13  */
14 
15 /**
16  * @file
17  * Recruitment Engine by flix
18  * See http://wiki.wesnoth.org/AI_Recruitment
19  */
20 
22 
23 #include "ai/actions.hpp"
24 #include "ai/composite/rca.hpp"
25 #include "ai/manager.hpp"
26 #include "actions/attack.hpp"
27 #include "attack_prediction.hpp"
28 #include "filter_context.hpp"
29 #include "game_board.hpp"
30 #include "game_display.hpp"
31 #include "log.hpp"
32 #include "map/map.hpp"
33 #include "map/label.hpp"
34 #include "pathfind/pathfind.hpp"
35 #include "pathutils.hpp"
36 #include "resources.hpp"
37 #include "team.hpp"
38 #include "tod_manager.hpp"
39 #include "units/filter.hpp"
40 #include "units/map.hpp"
41 #include "units/types.hpp"
42 #include "util.hpp"
43 #include "variable.hpp"
44 #include "wml_exception.hpp"
45 #include "config_assign.hpp"
46 
47 #include <boost/scoped_ptr.hpp>
48 #include <cmath>
49 
50 static lg::log_domain log_ai_recruitment("ai/recruitment");
51 #define LOG_AI_RECRUITMENT LOG_STREAM(info, log_ai_recruitment)
52 #define ERR_AI_RECRUITMENT LOG_STREAM(err, log_ai_recruitment)
53 
54 #ifdef _MSC_VER
55 #pragma warning(push)
56 // silence "inherits via dominance" warnings
57 #pragma warning(disable:4250)
58 #endif
59 
60 namespace ai {
61 
62 namespace default_recruitment {
63 
64 namespace {
65 /**
66  * CONSTANTS
67  */
68 
69 // This is used for a income estimation. We'll calculate the estimated income of this much
70 // future turns and decide if we'd gain gold if we start to recruit no units anymore.
71 const static int SAVE_GOLD_FORECAST_TURNS = 5;
72 
73 // When a team has less then this much units, consider recruit-list too.
74 const static unsigned int UNIT_THRESHOLD = 5;
75 
76 // Defines the shape of the border-zone between enemies.
77 // Higher values mean more important hexes.
78 const static double MAP_BORDER_THICKNESS = 2.0;
79 const static double MAP_BORDER_WIDTH = 0.2;
80 
81 // This parameter can be used to shift all important hexes in one directon.
82 // For example if our AI should act rather defensivly we may want to set
83 // this value to a negative number. Then the AI will more care about hexes
84 // nearer to the own units.
85 const static int MAP_OFFENSIVE_SHIFT = 0;
86 
87 // When villages are this near to imprtant hexes they count as important.
88 const static int MAP_VILLAGE_NEARNESS_THRESHOLD = 3;
89 
90 // Radius of area around important villages.
91 const static int MAP_VILLAGE_SURROUNDING = 1;
92 
93 // Determines the power of a raw unit comparison
94 // A higher power means that *very good* units will be
95 // stronger favored compared to just *good* units.
96 const static double COMBAT_SCORE_POWER = 1.;
97 
98 // A cache is used to store the simulation results.
99 // This value determines how much the average defenses of the important hexes can differ
100 // until the simulation will run again.
101 const static double COMBAT_CACHE_TOLERANCY = 0.5;
102 
103 // The old recruitment CA usually recruited too many scouts.
104 // To prevent this we multiply the aspect village_per_scout with this constant.
105 const static double VILLAGE_PER_SCOUT_MULTIPLICATOR = 2.;
106 }
107 
109  : candidate_action(context, cfg),
110  important_hexes_(),
111  important_terrain_(),
112  own_units_in_combat_counter_(0),
113  average_local_cost_(),
114  cheapest_unit_costs_(),
115  combat_cache_(),
116  recruit_situation_change_observer_(),
117  average_lawful_bonus_(0.0),
118  recruitment_instructions_(),
119  recruitment_instructions_turn_(-1),
120  own_units_count_(),
121  total_own_units_(0),
122  scouts_wanted_(0)
123 {
124  if (cfg["state"] == "save_gold") {
125  state_ = SAVE_GOLD;
126  } else if (cfg["state"] == "spend_all_gold") {
128  } else {
129  state_ = NORMAL;
130  }
131 }
132 
135  if (state_ == SAVE_GOLD) {
136  cfg["state"] = "save_gold";
137  } else if (state_ == SPEND_ALL_GOLD) {
138  cfg["state"] = "spend_all_gold";
139  } else {
140  cfg["state"] = "normal";
141  }
142  return cfg;
143 }
144 
146  // Check if the recruitment list has changed.
147  // Then cheapest_unit_costs_ is not valid anymore.
149  cheapest_unit_costs_.clear();
151  }
152 
153  // When evaluate() is called the first time this turn,
154  // we'll retrieve the recruitment-instruction aspect.
159  LOG_AI_RECRUITMENT << "Recruitment-instructions updated:\n";
161  }
162 
163  // Check if we have something to do.
164  const config* job = get_most_important_job();
165  if (!job) {
166  return BAD_SCORE;
167  }
168 
169  const unit_map& units = *resources::units;
170  const std::vector<unit_map::const_iterator> leaders = units.find_leaders(get_side());
171 
172  for (const unit_map::const_iterator& leader : leaders) {
173  if (leader == resources::units->end()) {
174  return BAD_SCORE;
175  }
176  // Check Gold. But proceed if there is a unit with cost <= 0 (WML can do that)
177  int cheapest_unit_cost = get_cheapest_unit_cost_for_leader(leader);
178  if (current_team().gold() < cheapest_unit_cost && cheapest_unit_cost > 0) {
179  continue;
180  }
181 
182  const map_location& loc = leader->get_location();
183  if (resources::gameboard->map().is_keep(loc) &&
185  return get_score();
186  }
187  }
188 
189  return BAD_SCORE;
190 }
191 
193  LOG_AI_RECRUITMENT << "\n\n\n------------AI RECRUITMENT BEGIN---------------\n\n";
194  LOG_AI_RECRUITMENT << "TURN: " << resources::tod_manager->turn() <<
195  " SIDE: " << current_team().side() << "\n";
196 
197  /*
198  * Check which leaders can recruit and collect them in leader_data.
199  */
200 
201  const unit_map& units = *resources::units;
202  const gamemap& map = resources::gameboard->map();
203  const std::vector<unit_map::const_iterator> leaders = units.find_leaders(get_side());
204 
205  // This is the central datastructure with all score_tables in it.
206  std::vector<data> leader_data;
207 
208  std::set<std::string> global_recruits;
209 
210  for (const unit_map::const_iterator& leader : leaders) {
211  const map_location& keep = leader->get_location();
212  if (!resources::gameboard->map().is_keep(keep)) {
213  LOG_AI_RECRUITMENT << "Leader " << leader->name() << " is not on keep. \n";
214  continue;
215  }
217  LOG_AI_RECRUITMENT << "Leader " << leader->name() << " has no free hexes \n";
218  continue;
219  }
220  int cheapest_unit_cost = get_cheapest_unit_cost_for_leader(leader);
221  if (current_team().gold() < cheapest_unit_cost && cheapest_unit_cost > 0) {
222  LOG_AI_RECRUITMENT << "Leader " << leader->name() << " recruits are too expensive. \n";
223  continue;
224  }
225 
226  // Leader can recruit.
227 
228  data data(leader);
229 
230  // Add team recruits.
231  for (const std::string& recruit : current_team().recruits()) {
232  if (!unit_types.find(recruit)) {
233  lg::wml_error() << "Unit-type \"" << recruit << "\" doesn't exist.\n";
234  }
235  data.recruits.insert(recruit);
236  data.scores[recruit] = 0.0;
237  global_recruits.insert(recruit);
238  }
239 
240  // Add extra recruits.
241  for (const std::string& recruit : leader->recruits()) {
242  if (!unit_types.find(recruit)) {
243  lg::wml_error() << "Unit-type \"" << recruit << "\" doesn't exist.\n";
244  }
245  data.recruits.insert(recruit);
246  data.scores[recruit] = 0.0;
247  global_recruits.insert(recruit);
248  }
249 
250  // Add recalls.
251  // Recalls are treated as recruits. While recruiting
252  // we'll check if we can do a recall instead of a recruitment.
253  for (const unit_const_ptr & recall : current_team().recall_list()) {
254  // Check if this leader is allowed to recall this unit.
255  const unit_filter ufilt( vconfig(leader->recall_filter()), resources::filter_con);
256  if (!ufilt(*recall, map_location::null_location())) {
257  continue;
258  }
259  data.recruits.insert(recall->type_id());
260  data.scores[recall->type_id()] = 0.0;
261  global_recruits.insert(recall->type_id());
262  }
263 
264  // Check if leader is in danger. (If a enemies unit can attack the leader)
265  data.in_danger = power_projection(leader->get_location(), get_enemy_dstsrc()) > 0;
266 
267  // If yes, set ratio_score very high, so this leader will get priority while recruiting.
268  if (data.in_danger) {
269  data.ratio_score = 50;
271  LOG_AI_RECRUITMENT << "Leader " << leader->name() << " is in danger.\n";
272  }
273 
274  leader_data.push_back(data);
275  }
276 
277  if (leader_data.empty()) {
278  LOG_AI_RECRUITMENT << "No leader available for recruiting. \n";
279  return; // This CA is going to be blacklisted for this turn.
280  }
281 
282  if (global_recruits.empty()) {
283  LOG_AI_RECRUITMENT << "All leaders have empty recruitment lists. \n";
284  return; // This CA is going to be blacklisted for this turn.
285  }
286 
287  /**
288  * Find important hexes and calculate other static things.
289  */
290 
292  // Show "x" on important hexes if debug mode is activated AND
293  // the log domain "ai/recruitment" is used.
294  if (game_config::debug && !lg::info().dont_log(log_ai_recruitment)) {
296  }
297 
298  for (const map_location& hex : important_hexes_) {
299  ++important_terrain_[map[hex]];
300  }
301 
305 
306  /**
307  * Fill scores.
308  */
309 
310  do_combat_analysis(&leader_data);
311 
312  LOG_AI_RECRUITMENT << "Scores before extra treatments:\n";
313  for (const data& data : leader_data) {
314  LOG_AI_RECRUITMENT << "\n" << data.to_string();
315  }
316 
317  do_similarity_penalty(&leader_data);
318  do_randomness(&leader_data);
319  handle_recruitment_more(&leader_data);
320 
321  LOG_AI_RECRUITMENT << "Scores after extra treatments:\n";
322  for (const data& data : leader_data) {
323  LOG_AI_RECRUITMENT << "\n" << data.to_string();
324  }
325 
326  /**
327  * Do recruitment according to [recruit]-tags and scores.
328  * Note that the scores don't indicate the preferred mix to recruit but rather
329  * the preferred mix of all units. So already existing units are considered.
330  */
331 
333  config* job = nullptr;
334  do { // Recruitment loop
336 
337  // Check if we may want to save gold by not recruiting.
338  update_state();
339  int save_gold_turn = get_recruitment_save_gold()["active"].to_int(2); // From aspect.
340  int current_turn = resources::tod_manager->turn();
341  bool save_gold_active = save_gold_turn > 0 && save_gold_turn <= current_turn;
342  if (state_ == SAVE_GOLD && save_gold_active) {
343  break;
344  }
345 
346  job = get_most_important_job();
347  if (!job) {
348  LOG_AI_RECRUITMENT << "All recruitment jobs (recruitment_instructions) done.\n";
349  break;
350  }
351  LOG_AI_RECRUITMENT << "Executing this job:\n" << *job << "\n";
352 
353  data* best_leader_data = get_best_leader_from_ratio_scores(leader_data, job);
354  if (!best_leader_data) {
355  LOG_AI_RECRUITMENT << "Leader with job (recruitment_instruction) is not on keep.\n";
356  if (remove_job_if_no_blocker(job)) {
357  continue;
358  } else {
359  break;
360  }
361  }
362  LOG_AI_RECRUITMENT << "We want to have " << scouts_wanted_ << " more scouts.\n";
363 
364  const std::string best_recruit = get_best_recruit_from_scores(*best_leader_data, job);
365  if (best_recruit.empty()) {
366  LOG_AI_RECRUITMENT << "Cannot fulfill recruitment-instruction.\n";
367  if (remove_job_if_no_blocker(job)) {
368  continue;
369  } else {
370  break;
371  }
372  }
373 
374  LOG_AI_RECRUITMENT << "Best recruit is: " << best_recruit << "\n";
375  const std::string* recall_id = get_appropriate_recall(best_recruit, *best_leader_data);
376  if (recall_id) {
377  LOG_AI_RECRUITMENT << "Found appropriate recall with id: " << *recall_id << "\n";
378  action_result = execute_recall(*recall_id, *best_leader_data);
379  } else {
380  action_result = execute_recruit(best_recruit, *best_leader_data);
381  }
382 
383  if (action_result->is_ok()) {
384  ++own_units_count_[best_recruit];
386  if (recruit_matches_type(best_recruit, "scout")) {
387  --scouts_wanted_;
388  }
389 
390  // Update the current job.
391  if (!job->operator[]("total").to_bool(false)) {
392  job->operator[]("number") = job->operator[]("number").to_int(99999) - 1;
393  }
394 
395  // Check if something changed in the recruitment list (WML can do that).
396  // If yes, just return/break. evaluate() and execute() will be called again.
398  break;
399  }
400  // Check if the gamestate changed more than once.
401  // (Recruitment will trigger one gamestate change, WML could trigger more changes.)
402  // If yes, just return/break. evaluate() and execute() will be called again.
404  break;
405  }
406 
407  } else {
408  LOG_AI_RECRUITMENT << "Recruit result not ok.\n";
409  // We'll end up here if
410  // 1. We haven't enough gold,
411  // 2. There aren't any free hexes around leaders,
412  // 3. This leader can not recruit this type (this can happen after a recall)
413  }
414  } while((action_result && action_result->is_ok()) || !action_result);
415  // A action_result may be uninitialized if a job was removed. Continue then anyway.
416 
417  // Recruiting is done now.
418  // Update state_ for next execution().
419 
420  if (state_ == LEADER_IN_DANGER) {
421  state_ = NORMAL;
422  }
423 
424  int status = (action_result) ? action_result->get_status() : -1;
425  bool no_gold = (status == recruit_result::E_NO_GOLD || status == recall_result::E_NO_GOLD);
426  if (state_ == SPEND_ALL_GOLD && no_gold) {
427  state_ = SAVE_GOLD;
428  }
429  if (job && no_gold) {
431  }
432 }
433 
434 /**
435  * A helper function for execute().
436  */
439  recall_result = check_recall_action(id, map_location::null_location(),
440  leader_data.leader->get_location());
441  if (recall_result->is_ok()) {
442  recall_result->execute();
443  ++leader_data.recruit_count;
444  }
445  return recall_result;
446 }
447 
448 /**
449  * A helper function for execute().
450  */
453  recruit_result = check_recruit_action(type, map_location::null_location(),
454  leader_data.leader->get_location());
455 
456  if (recruit_result->is_ok()) {
457  recruit_result->execute();
458  LOG_AI_RECRUITMENT << "Recruited " << type << "\n";
459  ++leader_data.recruit_count;
460  }
461  return recruit_result;
462 }
463 
464 /**
465  * A helper function for execute().
466  * Checks if this unit type can be recalled.
467  * If yes, we calculate a estimated value in gold of the recall unit.
468  * If this value is less then the recall cost, we dismiss the unit.
469  * The unit with the highest value will be returned.
470  */
472  const data& leader_data) const {
473  const std::string* best_recall_id = nullptr;
474  double best_recall_value = -1;
475  for (const unit_const_ptr & recall_unit : current_team().recall_list()) {
476  if (type != recall_unit->type_id()) {
477  continue;
478  }
479  // Check if this leader is allowed to recall this unit.
480  const unit_filter ufilt(vconfig(leader_data.leader->recall_filter()), resources::filter_con);
481  if (!ufilt(*recall_unit, map_location::null_location())) {
482  LOG_AI_RECRUITMENT << "Refused recall because of filter: " << recall_unit->id() << "\n";
483  continue;
484  }
485  double average_cost_of_advanced_unit = 0;
486  int counter = 0;
487  for (const std::string& advancement : recall_unit->advances_to()) {
488  const unit_type* advancement_type = unit_types.find(advancement);
489  if (!advancement_type) {
490  continue;
491  }
492  average_cost_of_advanced_unit += advancement_type->cost();
493  ++counter;
494  }
495  if (counter > 0) {
496  average_cost_of_advanced_unit /= counter;
497  } else {
498  // Unit don't have advancements. Use cost of unit itself.
499  average_cost_of_advanced_unit = recall_unit->cost();
500  }
501  double xp_quantity = static_cast<double>(recall_unit->experience()) /
502  recall_unit->max_experience();
503  double recall_value = recall_unit->cost() + xp_quantity * average_cost_of_advanced_unit;
504  if (recall_value < current_team().recall_cost()) {
505  continue; // Unit is not worth to get recalled.
506  }
507  if (recall_value > best_recall_value) {
508  best_recall_id = &recall_unit->id();
509  best_recall_value = recall_value;
510  }
511  }
512  return best_recall_id;
513 }
514 
515 /**
516  * A helper function for execute().
517  * Decides according to the leaders ratio scores which leader should recruit.
518  */
519 data* recruitment::get_best_leader_from_ratio_scores(std::vector<data>& leader_data,
520  const config* job) const {
521  assert(job);
522  // Find things for normalization.
523  int total_recruit_count = 0;
524  double ratio_score_sum = 0.0;
525  for (const data& data : leader_data) {
526  ratio_score_sum += data.ratio_score;
527  total_recruit_count += data.recruit_count;
528  }
529  assert(ratio_score_sum > 0.0);
530 
531  // Shuffle leader_data to break ties randomly.
532  std::random_shuffle(leader_data.begin(), leader_data.end());
533 
534  // Find which leader should recruit according to ratio_scores.
535  data* best_leader_data = nullptr;
536  double biggest_difference = -99999.;
537  for (data& data : leader_data) {
538  if (!leader_matches_job(data, job)) {
539  continue;
540  }
541  double desired_ammount = data.ratio_score / ratio_score_sum * (total_recruit_count + 1);
542  double current_ammount = data.recruit_count;
543  double difference = desired_ammount - current_ammount;
544  if (difference > biggest_difference) {
545  biggest_difference = difference;
546  best_leader_data = &data;
547  }
548  }
549  return best_leader_data;
550 }
551 
552 /**
553  * A helper function for execute().
554  * Counts own units and then decides what unit should be recruited so that the
555  * unit distribution approaches the given scores.
556  */
558  const config* job) {
559  assert(job);
560  std::string pattern_type = get_random_pattern_type_if_exists(leader_data, job);
561  if (!pattern_type.empty()) {
562  LOG_AI_RECRUITMENT << "Randomly chosen pattern_type: " << pattern_type << "\n";
563  }
564  std::string best_recruit = "";
565  double biggest_difference = -99999.;
566  for (const score_map::value_type& i : leader_data.get_normalized_scores()) {
567  const std::string& unit = i.first;
568  const double score = i.second;
569 
570  if (!limit_ok(unit)) {
571  continue;
572  }
573  if (!pattern_type.empty()) {
574  if (!recruit_matches_type(unit, pattern_type)) {
575  continue;
576  }
577  } else {
578  if (!recruit_matches_job(unit, job)) {
579  continue;
580  }
581  }
582 
583  double desired_ammount = score * (total_own_units_ + 1);
584  double current_ammount = own_units_count_[unit];
585  double difference = desired_ammount - current_ammount;
586  if (scouts_wanted_ > 0 && recruit_matches_type(unit, "scout")) {
587  difference += 1000.;
588  }
589  if (difference > biggest_difference) {
590  biggest_difference = difference;
591  best_recruit = unit;
592  }
593  }
594  return best_recruit;
595 }
596 
597 /**
598  * For Map Analysis
599  * Computes from our cost map and the combined cost map of all enemies the important hexes.
600  */
602  const pathfind::full_cost_map& my_cost_map,
603  const pathfind::full_cost_map& enemy_cost_map) {
604 
605  const gamemap& map = resources::gameboard->map();
606 
607  // First collect all hexes where the average costs are similar in important_hexes_candidates
608  // Then chose only those hexes where the average costs are relatively low.
609  // This is done to remove hexes to where the teams need a similar amount of moves but
610  // which are relatively far away comparing to other important hexes.
611  typedef std::map<map_location, double> border_cost_map;
612  border_cost_map important_hexes_candidates;
613  double smallest_border_movecost = 999999;
614  double biggest_border_movecost = 0;
615  for(int x = 0; x < map.w(); ++x) {
616  for (int y = 0; y < map.h(); ++y) {
617  double my_cost_average = my_cost_map.get_average_cost_at(x, y);
618  double enemy_cost_average = enemy_cost_map.get_average_cost_at(x, y);
619  if (my_cost_average == -1 || enemy_cost_average == -1) {
620  continue;
621  }
622  // We multiply the threshold MAP_BORDER_THICKNESS by the average_local_cost
623  // to favor high cost hexes (a bit).
624  if (std::abs(my_cost_average - MAP_OFFENSIVE_SHIFT - enemy_cost_average) <
625  MAP_BORDER_THICKNESS * average_local_cost_[map_location(x, y)]) {
626  double border_movecost = (my_cost_average + enemy_cost_average) / 2;
627 
628  important_hexes_candidates[map_location(x, y)] = border_movecost;
629 
630  if (border_movecost < smallest_border_movecost) {
631  smallest_border_movecost = border_movecost;
632  }
633  if (border_movecost > biggest_border_movecost) {
634  biggest_border_movecost = border_movecost;
635  }
636  }
637  } // for
638  } // for
639  double threshold = (biggest_border_movecost - smallest_border_movecost) *
640  MAP_BORDER_WIDTH + smallest_border_movecost;
641  for (const border_cost_map::value_type& candidate : important_hexes_candidates) {
642  if (candidate.second < threshold) {
643  important_hexes_.insert(candidate.first);
644  }
645  }
646 }
647 
648 /**
649  * For Map Analysis.
650  * Calculates for a given unit the average defense on the map.
651  * (According to important_hexes_ / important_terrain_)
652  */
653 double recruitment::get_average_defense(const std::string& u_type) const {
654  const unit_type* const u_info = unit_types.find(u_type);
655  if (!u_info) {
656  return 0.0;
657  }
658  long summed_defense = 0;
659  int total_terrains = 0;
660  for (const terrain_count_map::value_type& entry : important_terrain_) {
661  const t_translation::t_terrain& terrain = entry.first;
662  int count = entry.second;
663  int defense = 100 - u_info->movement_type().defense_modifier(terrain);
664  summed_defense += defense * count;
665  total_terrains += count;
666  }
667  double average_defense = (total_terrains == 0) ? 0.0 :
668  static_cast<double>(summed_defense) / total_terrains;
669  return average_defense;
670 }
671 
672 /**
673  * For Map Analysis.
674  * Creates cost maps for a side. Each hex is map to
675  * a) the summed movecost and
676  * b) how many units can reach this hex
677  * for all units of side.
678  */
680  const unit_map& units = *resources::units;
681  const team& team = (*resources::teams)[side - 1];
682 
683  pathfind::full_cost_map cost_map(true, true, team, true, true);
684 
685  // First add all existing units to cost_map.
686  unsigned int unit_count = 0;
687  for (const unit& unit : units) {
688  if (unit.side() != side || unit.can_recruit() ||
689  unit.incapacitated() || unit.total_movement() <= 0) {
690  continue;
691  }
692  ++unit_count;
693  cost_map.add_unit(unit);
694  }
695 
696  // If this side has not so many units yet, add unit_types with the leaders position as origin.
697  if (unit_count < UNIT_THRESHOLD) {
698  std::vector<unit_map::const_iterator> leaders = units.find_leaders(side);
699  for (const unit_map::const_iterator& leader : leaders) {
700  // First add team-recruits (it's fine when (team-)recruits are added multiple times).
701  for (const std::string& recruit : team.recruits()) {
702  cost_map.add_unit(leader->get_location(), unit_types.find(recruit), side);
703  }
704 
705  // Next add extra-recruits.
706  for (const std::string& recruit : leader->recruits()) {
707  cost_map.add_unit(leader->get_location(), unit_types.find(recruit), side);
708  }
709  }
710  }
711  return cost_map;
712 }
713 
714 /**
715  * For Map Analysis.
716  * Shows the important hexes for debugging purposes on the map. Only if debug is activated.
717  */
719  if (!game_config::debug) {
720  return;
721  }
723  for (const map_location& loc : important_hexes_) {
724  // Little hack: use map_location north from loc and make 2 linebreaks to center the "X".
725  resources::screen->labels().set_label(loc.get_direction(map_location::NORTH), "\n\nX");
726  }
727 }
728 
729 /**
730  * Calculates a average lawful bonus, so Combat Analysis will work
731  * better in caves and custom time of day cycles.
732  */
734  int sum = 0;
735  int counter = 0;
736  for (const time_of_day& time : resources::tod_manager->times()) {
737  sum += time.lawful_bonus;
738  ++counter;
739  }
740  if (counter > 0) {
741  average_lawful_bonus_ = round_double(static_cast<double>(sum) / counter);
742  }
743 }
744 
745 /**
746  * For Map Analysis.
747  * Creates a map where each hex is mapped to the average cost of the terrain for our units.
748  */
750  average_local_cost_.clear();
751  const gamemap& map = resources::gameboard->map();
752  const team& team = (*resources::teams)[get_side() - 1];
753 
754  for(int x = 0; x < map.w(); ++x) {
755  for (int y = 0; y < map.h(); ++y) {
756  map_location loc(x, y);
757  int summed_cost = 0;
758  int count = 0;
759  for (const std::string& recruit : team.recruits()) {
760  const unit_type* const unit_type = unit_types.find(recruit);
761  if (!unit_type) {
762  continue;
763  }
764  int cost = unit_type->movement_type().get_movement().cost(map[loc]);
765  if (cost < 99) {
766  summed_cost += cost;
767  ++count;
768  }
769  }
770  average_local_cost_[loc] = (count == 0) ? 0 : static_cast<double>(summed_cost) / count;
771  }
772  }
773 }
774 
775 /**
776  * For Map Analysis.
777  * Creates a std::set of hexes where a fight will occur with high probability.
778  */
780  important_hexes_.clear();
781  important_terrain_.clear();
783 
785  const gamemap& map = resources::gameboard->map();
786  const unit_map& units = *resources::units;
787 
788  // Mark battle areas as important
789  // This are locations where one of my units is adjacent
790  // to a enemies unit.
791  for (const unit& unit : units) {
792  if (unit.side() != get_side()) {
793  continue;
794  }
796  // We found a enemy next to us. Mark our unit and all adjacent
797  // hexes as important.
798  std::vector<map_location> surrounding;
799  get_tiles_in_radius(unit.get_location(), 1, surrounding);
801  std::copy(surrounding.begin(), surrounding.end(),
802  std::inserter(important_hexes_, important_hexes_.begin()));
804  }
805  }
806 
807  // Mark area between me and enemies as important
808  // This is done by creating a cost_map for each team.
809  // A cost_map maps to each hex the average costs to reach this hex
810  // for all units of the team.
811  // The important hexes are those where my value on the cost map is
812  // similar to a enemies one.
814  for (const team& team : *resources::teams) {
815  if (current_team().is_enemy(team.side())) {
816  const pathfind::full_cost_map enemy_cost_map = get_cost_map_of_side(team.side());
817 
818  compare_cost_maps_and_update_important_hexes(my_cost_map, enemy_cost_map);
819  }
820  }
821 
822  // Mark 'near' villages and area around them as important
823  // To prevent a 'feedback' of important locations collect all
824  // important villages first and add them and their surroundings
825  // to important_hexes_ in a second step.
826  std::vector<map_location> important_villages;
827  for (const map_location& village : map.villages()) {
828  std::vector<map_location> surrounding;
829  get_tiles_in_radius(village, MAP_VILLAGE_NEARNESS_THRESHOLD, surrounding);
830  for (const map_location& hex : surrounding) {
831  if (important_hexes_.find(hex) != important_hexes_.end()) {
832  important_villages.push_back(village);
833  break;
834  }
835  }
836  }
837  for (const map_location& village : important_villages) {
838  important_hexes_.insert(village);
839  std::vector<map_location> surrounding;
840  get_tiles_in_radius(village, MAP_VILLAGE_SURROUNDING, surrounding);
841  for (const map_location& hex : surrounding) {
842  // only add hex if one of our units can reach the hex
843  if (map.on_board(hex) && my_cost_map.get_cost_at(hex.x, hex.y) != -1) {
844  important_hexes_.insert(hex);
845  }
846  }
847  }
848 }
849 
850 /**
851  * For Combat Analysis.
852  * Calculates how good unit-type a is against unit type b.
853  * If the value is bigger then 0, a is better then b.
854  * If the value is 2.0 then unit-type a is twice as good as unit-type b.
855  * Since this function is called very often it uses a cache.
856  */
858  const unit_type* const type_a = unit_types.find(a);
859  const unit_type* const type_b = unit_types.find(b);
860  if (!type_a || !type_b) {
861  ERR_AI_RECRUITMENT << "Couldn't find unit type: " << ((type_a) ? b : a) << "." << std::endl;
862  return 0.0;
863  }
864  double defense_a = get_average_defense(a);
865  double defense_b = get_average_defense(b);
866 
867  const double* cache_value = get_cached_combat_value(a, b, defense_a, defense_b);
868  if (cache_value) {
869  return *cache_value;
870  }
871 
872  double damage_to_a = 0.0;
873  double damage_to_b = 0.0;
874 
875  // a attacks b
876  simulate_attack(type_a, type_b, defense_a, defense_b, &damage_to_a, &damage_to_b);
877  // b attacks a
878  simulate_attack(type_b, type_a, defense_b, defense_a, &damage_to_b, &damage_to_a);
879 
880  int a_cost = (type_a->cost() > 0) ? type_a->cost() : 1;
881  int b_cost = (type_b->cost() > 0) ? type_b->cost() : 1;
882  int a_max_hp = (type_a->hitpoints() > 0) ? type_a->hitpoints() : 1;
883  int b_max_hp = (type_b->hitpoints() > 0) ? type_b->hitpoints() : 1;
884 
885  double retval = 1.;
886  // There are rare cases where a unit deals 0 damage (eg. Elvish Lady).
887  // Then we just set the value to something reasonable.
888  if (damage_to_a <= 0 && damage_to_b <= 0) {
889  retval = 0.;
890  } else if (damage_to_a <= 0) {
891  retval = 2.;
892  } else if (damage_to_b <= 0) {
893  retval = -2.;
894  } else {
895  // Normal case
896  double value_of_a = damage_to_b / (b_max_hp * a_cost);
897  double value_of_b = damage_to_a / (a_max_hp * b_cost);
898 
899  if (value_of_a > value_of_b) {
900  return value_of_a / value_of_b;
901  } else if (value_of_a < value_of_b) {
902  return -value_of_b / value_of_a;
903  } else {
904  return 0.;
905  }
906  }
907 
908  // Insert in cache.
909  const cached_combat_value entry(defense_a, defense_b, retval);
910  std::set<cached_combat_value>& cache = combat_cache_[a][b];
911  cache.insert(entry);
912 
913  return retval;
914 }
915 
916 /**
917  * Combat Analysis.
918  * Main function.
919  * Compares all enemy units with all of our possible recruits and fills
920  * the scores.
921  */
922 void recruitment::do_combat_analysis(std::vector<data>* leader_data) {
923  const unit_map& units = *resources::units;
924 
925  // Collect all enemy units (and their hp) we want to take into account in enemy_units.
926  typedef std::vector<std::pair<std::string, int> > unit_hp_vector;
927  unit_hp_vector enemy_units;
928  for (const unit& unit : units) {
929  if (!current_team().is_enemy(unit.side()) || unit.incapacitated()) {
930  continue;
931  }
932  enemy_units.push_back(std::make_pair(unit.type_id(), unit.hitpoints()));
933  }
934  if (enemy_units.size() < UNIT_THRESHOLD) {
935  // Use also enemies recruitment lists and insert units into enemy_units.
936  for (const team& team : *resources::teams) {
937  if (!current_team().is_enemy(team.side())) {
938  continue;
939  }
940  std::set<std::string> possible_recruits;
941  // Add team recruits.
942  possible_recruits.insert(team.recruits().begin(), team.recruits().end());
943  // Add extra recruits.
944  const std::vector<unit_map::const_iterator> leaders = units.find_leaders(team.side());
945  for (unit_map::const_iterator leader : leaders) {
946  possible_recruits.insert(leader->recruits().begin(), leader->recruits().end());
947  }
948  // Insert set in enemy_units.
949  for (const std::string& possible_recruit : possible_recruits) {
950  const unit_type* recruit_type = unit_types.find(possible_recruit);
951  if (recruit_type) {
952  int hp = recruit_type->hitpoints();
953  enemy_units.push_back(std::make_pair(possible_recruit, hp));
954  }
955  }
956  }
957  }
958 
959  for (data& leader : *leader_data) {
960  if (leader.recruits.empty()) {
961  continue;
962  }
963  typedef std::map<std::string, double> simple_score_map;
964  simple_score_map temp_scores;
965 
966  for (const unit_hp_vector::value_type& entry : enemy_units) {
967  const std::string& enemy_unit = entry.first;
968  int enemy_unit_hp = entry.second;
969  for (const std::string& recruit : leader.recruits) {
970  double score = compare_unit_types(recruit, enemy_unit);
971  score *= enemy_unit_hp;
972  score = pow(score, COMBAT_SCORE_POWER);
973  temp_scores[recruit] += score;
974  }
975  }
976 
977  if (temp_scores.empty()) {
978  return;
979  }
980  // Find things for normalization.
981  double max = -99999.;
982  double sum = 0;
983  for (const simple_score_map::value_type& entry : temp_scores) {
984  double score = entry.second;
985  if (score > max) {
986  max = score;
987  }
988  sum += score;
989  }
990  double average = sum / temp_scores.size();
991 
992  // What we do now is a linear transformation.
993  // We want to map the scores in temp_scores to something between 0 and 100.
994  // The max score shall always be 100.
995  // The min score depends on the aspect "recruitment_diversity".
996  double new_100 = max;
997  double score_threshold = get_recruitment_diversity();
998  if (score_threshold <= 0) {
999  score_threshold = 0.0001;
1000  }
1001  double new_0 = max - (score_threshold * (max - average));
1002  if (new_100 == new_0) {
1003  // This can happen if max == average. (E.g. only one possible recruit)
1004  new_0 -= 0.000001;
1005  }
1006 
1007  for (const simple_score_map::value_type& entry : temp_scores) {
1008  const std::string& recruit = entry.first;
1009  double score = entry.second;
1010 
1011  // Here we transform.
1012  // (If score <= new_0 then normalized_score will be 0)
1013  // (If score = new_100 then normalized_score will be 100)
1014  double normalized_score = 100 * ((score - new_0) / (new_100 - new_0));
1015  if (normalized_score < 0) {
1016  normalized_score = 0;
1017  }
1018  leader.scores[recruit] += normalized_score;
1019  }
1020  } // for all leaders
1021 }
1022 
1023 /**
1024  * For Combat Analysis.
1025  * Returns the cached combat value for two unit types
1026  * or nullptr if there is none or terrain defenses are not within range.
1027  */
1029  double a_defense, double b_defense) {
1030  double best_distance = 999;
1031  const double* best_value = nullptr;
1032  const std::set<cached_combat_value>& cache = combat_cache_[a][b];
1033  for (const cached_combat_value& entry : cache) {
1034  double distance_a = std::abs(entry.a_defense - a_defense);
1035  double distance_b = std::abs(entry.b_defense - b_defense);
1036  if (distance_a <= COMBAT_CACHE_TOLERANCY && distance_b <= COMBAT_CACHE_TOLERANCY) {
1037  if(distance_a + distance_b <= best_distance) {
1038  best_distance = distance_a + distance_b;
1039  best_value = &entry.value;
1040  }
1041  }
1042  }
1043  return best_value;
1044 }
1045 
1046 /**
1047  * For Combat Analysis.
1048  * This struct encapsulates all information for one attack simulation.
1049  * One attack simulation is defined by the unit-types, the weapons and the units defenses.
1050  */
1058 
1059  attack_simulation(const unit_type* attacker, const unit_type* defender,
1060  double attacker_defense, double defender_defense,
1061  const attack_type* att_weapon, const attack_type* def_weapon,
1062  int average_lawful_bonus) :
1063  attacker_type(attacker),
1064  defender_type(defender),
1065  attacker_stats(attacker, att_weapon, true, defender, def_weapon,
1066  round_double(defender_defense), average_lawful_bonus),
1067  defender_stats(defender, def_weapon, false, attacker, att_weapon,
1068  round_double(attacker_defense), average_lawful_bonus),
1069  attacker_combatant(attacker_stats),
1070  defender_combatant(defender_stats)
1071  {
1072  attacker_combatant.fight(defender_combatant);
1073  }
1074 
1075  bool better_result(const attack_simulation* other, bool for_defender) {
1076  assert(other);
1077  if (for_defender) {
1079  defender_combatant, attacker_combatant,
1080  other->defender_combatant, other->attacker_combatant, 0);
1081  } else {
1083  attacker_combatant, defender_combatant,
1084  other->attacker_combatant, other->defender_combatant, 0);
1085  }
1086  }
1087 
1088  double get_avg_hp_of_defender() const {
1089  return get_avg_hp_of_combatant(false);
1090  }
1091 
1092  double get_avg_hp_of_attacker() const {
1093  return get_avg_hp_of_combatant(true);
1094  }
1095  double get_avg_hp_of_combatant(bool attacker) const {
1096  const combatant& combatant = (attacker) ? attacker_combatant : defender_combatant;
1097  const unit_type* unit_type = (attacker) ? attacker_type : defender_type;
1098  double avg_hp = combatant.average_hp(0);
1099 
1100  // handle poisson
1101  avg_hp -= combatant.poisoned * game_config::poison_amount;
1102 
1103  avg_hp = std::max(0., avg_hp);
1104  avg_hp = std::min(static_cast<double>(unit_type->hitpoints()), avg_hp);
1105  return avg_hp;
1106  }
1107 };
1108 
1109 /**
1110  * For Combat Analysis.
1111  * Simulates a attack with a attacker and a defender.
1112  * The function will use battle_context::better_combat() to decide which weapon to use.
1113  */
1115  const unit_type* const attacker, const unit_type* const defender,
1116  double attacker_defense, double defender_defense,
1117  double* damage_to_attacker, double* damage_to_defender) const {
1118  if(!attacker || !defender || !damage_to_attacker || !damage_to_defender) {
1119  ERR_AI_RECRUITMENT << "nullptr pointer in simulate_attack()" << std::endl;
1120  return;
1121  }
1122  const std::vector<attack_type> attacker_weapons = attacker->attacks();
1123  const std::vector<attack_type> defender_weapons = defender->attacks();
1124 
1125  boost::shared_ptr<attack_simulation> best_att_attack;
1126 
1127  // Let attacker choose weapon
1128  for (const attack_type& att_weapon : attacker_weapons) {
1129  boost::shared_ptr<attack_simulation> best_def_response;
1130  // Let defender choose weapon
1131  for (const attack_type& def_weapon : defender_weapons) {
1132  if (att_weapon.range() != def_weapon.range()) {
1133  continue;
1134  }
1136  attacker, defender,
1137  attacker_defense, defender_defense,
1138  &att_weapon, &def_weapon, average_lawful_bonus_));
1139  if (!best_def_response || simulation->better_result(best_def_response.get(), true)) {
1140  best_def_response = simulation;
1141  }
1142  } // for defender weapons
1143 
1144  if (!best_def_response) {
1145  // Defender can not fight back. Simulate this as well.
1146  best_def_response.reset(new attack_simulation(
1147  attacker, defender,
1148  attacker_defense, defender_defense,
1149  &att_weapon, nullptr, average_lawful_bonus_));
1150  }
1151  if (!best_att_attack || best_def_response->better_result(best_att_attack.get(), false)) {
1152  best_att_attack = best_def_response;
1153  }
1154  } // for attacker weapons
1155 
1156  if (!best_att_attack) {
1157  return;
1158  }
1159 
1160  *damage_to_defender += (defender->hitpoints() - best_att_attack->get_avg_hp_of_defender());
1161  *damage_to_attacker += (attacker->hitpoints() - best_att_attack->get_avg_hp_of_attacker());
1162 }
1163 
1164 /**
1165  * For Configuration / Aspect "recruitment-instructions"
1166  * We call a [recruit] tag a "job".
1167  */
1169  config* most_important_job = nullptr;
1170  int most_important_importance = -1;
1171  int biggest_number = -1;
1172  for (config& job : recruitment_instructions_.child_range("recruit")) {
1173  if (job.empty()) {
1174  continue;
1175  }
1176  int importance = job["importance"].to_int(1);
1177  int number = job["number"].to_int(99999);
1178  bool total = job["total"].to_bool(false);
1179  if (total) {
1180  // If the total flag is set we have to subtract
1181  // all existing units which matches the type.
1183  for (const count_map::value_type& entry : own_units_count_) {
1184  const std::string& unit_type = entry.first;
1185  const int count = entry.second;
1186  if (recruit_matches_job(unit_type, &job)) {
1187  number = number - count;
1188  }
1189  }
1190  }
1191  if (number <= 0) {
1192  continue;
1193  }
1194  if (importance > most_important_importance ||
1195  (importance == most_important_importance && biggest_number > number)) {
1196  most_important_job = &job;
1197  most_important_importance = importance;
1198  biggest_number = number;
1199  }
1200  }
1201  return most_important_job;
1202 }
1203 
1204 /**
1205  * For Configuration / Aspect "recruitment-instructions"
1206  * If the flag pattern is set, this method returns a random element of the
1207  * type-attribute.
1208  */
1210  const config* job) const {
1211  std::string choosen_type;
1212  if (job->operator[]("pattern").to_bool(false)) {
1213  std::vector<std::string> job_types = utils::split(job->operator[]("type"));
1214 
1215  if (job_types.empty()) {
1216  // Empty type attribute means random recruiting.
1217  // Fill job_types with recruitment list.
1218  std::copy(leader_data.recruits.begin(), leader_data.recruits.end(),
1219  std::back_inserter(job_types));
1220  }
1221 
1222  // Before we choose a random pattern type, we make sure that at least one recruit
1223  // matches the types and doesn't exceed the [limit].
1224  // We do this by erasing elements of job_types.
1225  std::vector<std::string>::iterator job_types_it = job_types.begin();
1226 
1227  // Iteration through all elements.
1228  while (job_types_it != job_types.end()) {
1229  bool type_ok = false;
1230  for (const std::string& recruit : leader_data.recruits) {
1231  if (recruit_matches_type(recruit, *job_types_it) && limit_ok(recruit)) {
1232  type_ok = true;
1233  break;
1234  }
1235  }
1236  if (type_ok) {
1237  ++job_types_it;
1238  } else {
1239  // Erase Element. erase() will return iterator of next element.
1240  LOG_AI_RECRUITMENT << "Erase type " << *job_types_it << " from pattern.\n";
1241  job_types_it = job_types.erase(job_types_it);
1242  }
1243  }
1244 
1245  if (!job_types.empty()) {
1246  // Choose a random job_type.
1247  choosen_type = job_types[rand() % job_types.size()];
1248  }
1249  }
1250  return choosen_type;
1251 }
1252 
1253 /**
1254  * For Configuration / Aspect "recruitment_pattern"
1255  * Converts the (old) recruitment_pattern into a recruitment_instruction (job).
1256  */
1258  const std::vector<std::string> recruitment_pattern = get_recruitment_pattern();
1259  if (recruitment_pattern.empty()) {
1260  return;
1261  }
1262  // Create a job (recruitment_instruction).
1263  config job;
1264  std::stringstream s;
1265  for (std::vector<std::string>::const_iterator type = recruitment_pattern.begin();
1266  type != recruitment_pattern.end(); ++type) {
1267  s << *type;
1268  if (type != recruitment_pattern.end() - 1) { // Avoid trailing comma.
1269  s << ", ";
1270  }
1271  }
1272  job["type"] = s.str();
1273  job["number"] = 99999;
1274  job["pattern"] = true;
1275  job["blocker"] = true;
1276  job["total"] = false;
1277  job["importance"] = 1;
1278  recruitment_instructions_.add_child("recruit", job);
1279 }
1280 
1281 /**
1282  * For Configuration / Aspect "recruitment-instructions"
1283  * Checks if a given leader is specified in the "leader_id" attribute.
1284  */
1285 bool recruitment::leader_matches_job(const data& leader_data, const config* job) const {
1286  assert(job);
1287  // First we make sure that this leader can recruit
1288  // at least one unit-type specified in the job.
1289  bool is_ok = false;
1290  for (const std::string& recruit : leader_data.recruits) {
1291  if (recruit_matches_job(recruit, job) && limit_ok(recruit)) {
1292  is_ok = true;
1293  break;
1294  }
1295  }
1296  if (!is_ok) {
1297  return false;
1298  }
1299 
1300  std::vector<std::string> ids = utils::split(job->operator[]("leader_id"));
1301  if (ids.empty()) {
1302  // If no leader is specified, all leaders are okay.
1303  return true;
1304  }
1305  return (std::find(ids.begin(), ids.end(), leader_data.leader->id()) != ids.end());
1306 }
1307 
1308 /**
1309  * For Configuration / Aspect "recruitment-instructions"
1310  * Checks if a recruit-type can be recruited according to the [limit]-tag.
1311  */
1312 bool recruitment::limit_ok(const std::string& recruit) const {
1313  // We don't use the member recruitment_instruction_ but instead
1314  // retrieve the aspect again. So the [limit]s can be altered during a turn.
1316 
1317  for (const config& limit : aspect.child_range("limit")) {
1318  std::vector<std::string> types = utils::split(limit["type"]);
1319  // First check if the recruit matches one of the types.
1320  if (recruit_matches_types(recruit, types)) {
1321  // Count all own existing units which matches the type.
1322  int count = 0;
1323  for (const count_map::value_type& entry : own_units_count_) {
1324  const std::string& unit = entry.first;
1325  int number = entry.second;
1326  if (recruit_matches_types(unit, types)) {
1327  count += number;
1328  }
1329  }
1330  // Check if we reached the limit.
1331  if (count >= limit["max"].to_int(0)) {
1332  return false;
1333  }
1334  }
1335  }
1336  return true;
1337 }
1338 
1339 /**
1340  * For Configuration / Aspect "recruitment-instructions"
1341  * Checks if a given recruit-type is specified in the "type" attribute.
1342  */
1343 bool recruitment::recruit_matches_job(const std::string& recruit, const config* job) const {
1344  assert(job);
1345  std::vector<std::string> job_types = utils::split(job->operator[]("type"));
1346  return recruit_matches_types(recruit, job_types);
1347 }
1348 
1349 /**
1350  * For Configuration / Aspect "recruitment-instructions"
1351  * Checks if a given recruit-type matches one atomic "type" attribute.
1352  */
1354  const unit_type* recruit_type = unit_types.find(recruit);
1355  if (!recruit_type) {
1356  return false;
1357  }
1358  // Consider type-name.
1359  if (recruit_type->id() == type) {
1360  return true;
1361  }
1362  // Consider usage.
1363  if (recruit_type->usage() == type) {
1364  return true;
1365  }
1366  // Consider level.
1367  std::stringstream s;
1368  s << recruit_type->level();
1369  if (s.str() == type) {
1370  return true;
1371  }
1372  return false;
1373 }
1374 
1375 /**
1376  * For Configuration / Aspect "recruitment-instructions"
1377  * Checks if a given recruit-type matches one of the given types.
1378  */
1380  const std::vector<std::string>& types) const {
1381  // If no type is specified, all recruits are okay.
1382  if (types.empty()) {
1383  return true;
1384  }
1385  for (const std::string& type : types) {
1386  if (recruit_matches_type(recruit, type)) {
1387  return true;
1388  }
1389  }
1390  return false;
1391 }
1392 
1393 /**
1394  * For Configuration / Aspect "recruitment-instructions"
1395  */
1397  assert(job);
1398  if (!job->operator[]("blocker").to_bool(true)) {
1399  LOG_AI_RECRUITMENT << "Canceling job.\n";
1400  job->clear();
1401  return true;
1402  } else {
1403  LOG_AI_RECRUITMENT << "Aborting recruitment.\n";
1404  return false;
1405  }
1406 }
1407 
1408 /**
1409  * For Aspect "recruitment_save_gold".
1410  * Guess the income over the next turns.
1411  * This doesn't need to be exact. In the end we are just interested if this value is
1412  * positive or negative.
1413  */
1415  const team& team = (*resources::teams)[get_side() - 1];
1416  const size_t own_villages = team.villages().size();
1417  const double village_gain = get_estimated_village_gain();
1418  const double unit_gain = get_estimated_unit_gain();
1419 
1420  double total_income = 0;
1421  for (int i = 1; i <= turns; ++i) {
1422  double income = (own_villages + village_gain * i) * game_config::village_income;
1423  double upkeep = resources::gameboard->side_upkeep(get_side()) + unit_gain * i -
1424  (own_villages + village_gain * i) * game_config::village_support;
1425  double resulting_income = team.base_income() + income - std::max(0., upkeep);
1426  total_income += resulting_income;
1427  }
1428  return total_income;
1429 }
1430 
1431 /**
1432  * For Aspect "recruitment_save_gold".
1433  * Guess how many units we will gain / loose over the next turns per turn.
1434  */
1436  return - own_units_in_combat_counter_ / 3.;
1437 }
1438 
1439 /**
1440  * For Aspect "recruitment_save_gold".
1441  * Guess how many villages we will gain over the next turns per turn.
1442  */
1444  const gamemap& map = resources::gameboard->map();
1445  int neutral_villages = 0;
1446  for (const map_location& village : map.villages()) {
1447  if (resources::gameboard->village_owner(village) == -1) {
1448  ++neutral_villages;
1449  }
1450  }
1451  return (neutral_villages / resources::teams->size()) / 4.;
1452 }
1453 
1454 /**
1455  * For Aspect "recruitment_save_gold".
1456  * Returns our_total_unit_costs / enemy_total_unit_costs.
1457  */
1459  const unit_map& units = *resources::units;
1460  double own_total_value = 0.;
1461  double team_total_value = 0.;
1462  double enemy_total_value = 0.;
1463  for (const unit& unit : units) {
1464  if (unit.incapacitated() || unit.total_movement() <= 0 || unit.can_recruit()) {
1465  continue;
1466  }
1467  double value = unit.cost() * unit.hitpoints() / unit.max_hitpoints();
1468  if (current_team().is_enemy(unit.side())) {
1469  enemy_total_value += value;
1470  } else {
1471  team_total_value += value;
1472  if (unit.side() == current_team().side()) {
1473  own_total_value += value;
1474  }
1475  }
1476  }
1477  int allies_count = 0;
1478  for (const team& team : *resources::teams) {
1479  if (!current_team().is_enemy(team.side())) {
1480  ++allies_count;
1481  }
1482  }
1483  // If only the leader is left, the values could be 0.
1484  // Catch those cases and return something reasonable.
1485  if ((own_total_value == 0. || team_total_value == 0) && enemy_total_value == 0.) {
1486  return 0.; // do recruit
1487  } else if (enemy_total_value == 0.) {
1488  return 999.; // save money
1489  }
1490 
1491  // We calculate two ratios: One for the team and one for just our self.
1492  // Then we return the minimum.
1493  // This prevents cases where side1 will recruit until the save_gold begin threshold
1494  // is reached, and side2 won't recruit anything. (assuming side1 and side2 are allied)
1495  double own_ratio = (own_total_value / enemy_total_value) * allies_count;
1496  double team_ratio = team_total_value / enemy_total_value;
1497  return std::min<double>(own_ratio, team_ratio);
1498 }
1499 
1500 /**
1501  * For Aspect "recruitment_save_gold".
1502  * Main method.
1503  */
1506  return;
1507  }
1508  // Retrieve from aspect.
1509  int spend_all_gold = get_recruitment_save_gold()["spend_all_gold"].to_int(-1);
1510  if (spend_all_gold > 0 && current_team().gold() >= spend_all_gold) {
1512  LOG_AI_RECRUITMENT << "Changed state_ to SPEND_ALL_GOLD. \n";
1513  return;
1514  }
1515  double ratio = get_unit_ratio();
1516  double income_estimation = 1.;
1517  if (!get_recruitment_save_gold()["save_on_negative_income"].to_bool(false)) {
1518  income_estimation = get_estimated_income(SAVE_GOLD_FORECAST_TURNS);
1519  }
1520  LOG_AI_RECRUITMENT << "Ratio is " << ratio << "\n";
1521  LOG_AI_RECRUITMENT << "Estimated income is " << income_estimation << "\n";
1522 
1523  // Retrieve from aspect.
1524  double save_gold_begin = get_recruitment_save_gold()["begin"].to_double(1.5);
1525  double save_gold_end = get_recruitment_save_gold()["end"].to_double(1.1);
1526 
1527  if (state_ == NORMAL && ratio > save_gold_begin && income_estimation > 0) {
1528  state_ = SAVE_GOLD;
1529  LOG_AI_RECRUITMENT << "Changed state to SAVE_GOLD.\n";
1530  } else if (state_ == SAVE_GOLD && ratio < save_gold_end) {
1531  state_ = NORMAL;
1532  LOG_AI_RECRUITMENT << "Changed state to NORMAL.\n";
1533  }
1534 }
1535 
1536 /**
1537  * Will add a random value between 0 and "recruitment_randomness"
1538  * to all recruits
1539  */
1540 void recruitment::do_randomness(std::vector<data>* leader_data) const {
1541  if (!leader_data) {
1542  return;
1543  }
1544  for (data& data : *leader_data) {
1545  for (score_map::value_type& entry : data.scores) {
1546  double& score = entry.second;
1547  score += (static_cast<double>(rand()) / RAND_MAX) * get_recruitment_randomness();
1548  }
1549  }
1550 }
1551 
1552 /**
1553  * Will give a penalty to similar units. Similar units are units in one advancement tree.
1554  * Example (Archer can advance to Ranger):
1555  * before after
1556  * Elvish Fighter: 50 50
1557  * Elvish Archer: 50 25
1558  * Elvish Ranger: 50 25
1559  */
1560 void recruitment::do_similarity_penalty(std::vector<data>* leader_data) const {
1561  if (!leader_data) {
1562  return;
1563  }
1564  for (data& data : *leader_data) {
1565  // First we count how many similarities each recruit have to other ones (in a map).
1566  // Some examples:
1567  // If unit A and unit B have nothing to do with each other, they have similarity = 0.
1568  // If A advances to B both have similarity = 1.
1569  // If A advances to B and B to C, A, B and C have similarity = 2.
1570  // If A advances to B or C, A have similarity = 2. B and C have similarity = 1.
1571  typedef std::map<std::string, int> similarity_map;
1572  similarity_map similarities;
1573  for (const score_map::value_type& entry : data.scores) {
1574  const std::string& recruit = entry.first;
1575  const unit_type* recruit_type = unit_types.find(recruit);
1576  if (!recruit_type) {
1577  continue;
1578  }
1579  for (const std::string& advanced_type : recruit_type->advancement_tree()) {
1580  if (data.scores.count(advanced_type) != 0) {
1581  ++similarities[recruit];
1582  ++similarities[advanced_type];
1583  }
1584  }
1585  }
1586  // Now we divide each score by similarity + 1.
1587  for (score_map::value_type& entry : data.scores) {
1588  const std::string& recruit = entry.first;
1589  double& score = entry.second;
1590  score /= (similarities[recruit] + 1);
1591  }
1592  }
1593 }
1594 
1595 /**
1596  * Called at the beginning and whenever the recruitment list changes.
1597  */
1599  std::map<size_t, int>::const_iterator it = cheapest_unit_costs_.find(leader->underlying_id());
1600  if (it != cheapest_unit_costs_.end()) {
1601  return it->second;
1602  }
1603 
1604  int cheapest_cost = 999999;
1605 
1606  // team recruits
1607  for (const std::string& recruit : current_team().recruits()) {
1608  const unit_type* const info = unit_types.find(recruit);
1609  if (!info) {
1610  continue;
1611  }
1612  if (info->cost() < cheapest_cost) {
1613  cheapest_cost = info->cost();
1614  }
1615  }
1616  // extra recruits
1617  for (const std::string& recruit : leader->recruits()) {
1618  const unit_type* const info = unit_types.find(recruit);
1619  if (!info) {
1620  continue;
1621  }
1622  if (info->cost() < cheapest_cost) {
1623  cheapest_cost = info->cost();
1624  }
1625  }
1626  // Consider recall costs.
1627  if (!current_team().recall_list().empty() && current_team().recall_cost() < cheapest_cost) {
1628  cheapest_cost = current_team().recall_cost();
1629  }
1630  LOG_AI_RECRUITMENT << "Cheapest unit cost updated to " << cheapest_cost << ".\n";
1631  cheapest_unit_costs_[leader->underlying_id()] = cheapest_cost;
1632  return cheapest_cost;
1633 }
1634 
1635 /**
1636  * For Aspect "recruitment_more"
1637  */
1638 void recruitment::handle_recruitment_more(std::vector<data>* leader_data) const {
1639  if (!leader_data) {
1640  return;
1641  }
1642  const std::vector<std::string> aspect = get_recruitment_more();
1643  for (const std::string& type : aspect) {
1644  for (data& data : *leader_data) {
1645  for (score_map::value_type& entry : data.scores) {
1646  const std::string& recruit = entry.first;
1647  double& score = entry.second;
1648  if (recruit_matches_type(recruit, type)) {
1649  score += 25.;
1650  }
1651  }
1652  }
1653  }
1654 }
1655 
1656 /**
1657  * Helper function.
1658  * Returns true if there is a enemy within the radius.
1659  */
1660 bool recruitment::is_enemy_in_radius(const map_location& loc, int radius) const {
1661  const unit_map& units = *resources::units;
1662  std::vector<map_location> surrounding;
1663  get_tiles_in_radius(loc, radius, surrounding);
1664  if (surrounding.empty()) {
1665  return false;
1666  }
1667  for (const map_location& loc : surrounding) {
1668  const unit_map::const_iterator& enemy_it = units.find(loc);
1669  if(enemy_it == units.end()) {
1670  continue;
1671  }
1672  if (!current_team().is_enemy(enemy_it->side()) || enemy_it->incapacitated()) {
1673  continue;
1674  }
1675  return true;
1676  }
1677  return false;
1678 }
1679 
1680 /*
1681  * Helper Function.
1682  * Counts own units on the map and saves the result
1683  * in member own_units_count_
1684  */
1686  own_units_count_.clear();
1687  total_own_units_ = 0;
1688  const unit_map& units = *resources::units;
1689  for (const unit& unit : units) {
1690  if (unit.side() != get_side() || unit.can_recruit() ||
1691  unit.incapacitated() || unit.total_movement() <= 0) {
1692  continue;
1693  }
1695  ++total_own_units_;
1696  }
1697 }
1698 
1699 /**
1700  * This function will use the aspect villages_per_scout to decide how many
1701  * scouts we want to recruit.
1702  */
1704  scouts_wanted_ = 0;
1705  if (get_villages_per_scout() == 0) {
1706  return;
1707  }
1708  int neutral_villages = 0;
1709  // We recruit the initial allocation of scouts
1710  // based on how many neutral villages there are.
1711  for (const map_location& village : resources::gameboard->map().villages()) {
1712  if (resources::gameboard->village_owner(village) == -1) {
1713  ++neutral_villages;
1714  }
1715  }
1716  double our_share = static_cast<double>(neutral_villages) / resources::teams->size();
1717 
1718  // The villages per scout is for a two-side battle,
1719  // accounting for all neutral villages on the map.
1720  // We only look at our share of villages, so we halve it,
1721  // making us get twice as many scouts.
1722  double villages_per_scout = (VILLAGE_PER_SCOUT_MULTIPLICATOR * get_villages_per_scout()) / 2;
1723 
1724  scouts_wanted_ = (villages_per_scout > 0) ? round_double(our_share / villages_per_scout) : 0;
1725 
1726  if (scouts_wanted_ == 0) {
1727  return;
1728  }
1729 
1730  // Subtract already recruited scouts.
1731  for (const count_map::value_type& entry : own_units_count_) {
1732  const std::string& unit_type = entry.first;
1733  const int count = entry.second;
1734  if (recruit_matches_type(unit_type, "scout")) {
1735  scouts_wanted_ -= count;
1736  }
1737  }
1738 }
1739 
1740 /**
1741  * Observer Code
1742  */
1744  : recruit_list_changed_(false), gamestate_changed_(0) {
1747 }
1748 
1750  const std::string& event) {
1751  if (event == "ai_recruit_list_changed") {
1752  LOG_AI_RECRUITMENT << "Recruitment List is not valid anymore.\n";
1753  set_recruit_list_changed(true);
1754  } else {
1755  ++gamestate_changed_;
1756  }
1757 }
1758 
1762 }
1763 
1765  return recruit_list_changed_;
1766 }
1767 
1769  recruit_list_changed_ = changed;
1770 }
1771 
1773  return gamestate_changed_;
1774 }
1775 
1777  gamestate_changed_ = 0;
1778 }
1779 
1781  : standard_aspect<config>(context, cfg, id)
1782 {
1783  config parsed_cfg(cfg.has_child("value") ? cfg.child("value") : cfg);
1784  // First, transform simplified tags into [recruit] tags.
1785  for (config pattern : parsed_cfg.child_range("pattern")) {
1786  parsed_cfg["pattern"] = true;
1787  parsed_cfg.add_child("recruit", pattern);
1788  }
1789  for (config total : parsed_cfg.child_range("total")) {
1790  parsed_cfg["total"] = true;
1791  parsed_cfg.add_child("recruit", total);
1792  }
1793  parsed_cfg.clear_children("pattern", "total");
1794  // Then, if there's no [recruit], add one.
1795  if (!parsed_cfg.has_child("recruit")) {
1796  parsed_cfg.add_child("recruit", config_of("importance", 0));
1797  }
1798  // Finally, populate our lists
1799  for (config job : parsed_cfg.child_range("recruit")) {
1800  create_job(jobs_, job);
1801  }
1802  for (config lim : parsed_cfg.child_range("limit")) {
1803  create_limit(limits_, lim);
1804  }
1805  std::function<void(std::vector<boost::shared_ptr<recruit_job> >&, const config&)> factory_jobs =
1806  std::bind(&recruitment_aspect::create_job,*this,_1,_2);
1807  std::function<void(std::vector<boost::shared_ptr<recruit_limit> >&, const config&)> factory_limits =
1808  std::bind(&recruitment_aspect::create_limit,*this,_1,_2);
1809  register_vector_property(property_handlers(), "recruit", jobs_, factory_jobs);
1810  register_vector_property(property_handlers(), "limit", limits_, factory_limits);
1811 }
1812 
1814  config cfg;
1815  for (const boost::shared_ptr<recruit_job>& job : jobs_) {
1816  cfg.add_child("recruit", job->to_config());
1817  }
1818  for (const boost::shared_ptr<recruit_limit>& lim : limits_) {
1819  cfg.add_child("limit", lim->to_config());
1820  }
1821  *this->value_ = cfg;
1822  this->valid_ = true;
1823 }
1824 
1827  utils::split(job["type"]),
1828  job["leader_id"], job["id"],
1829  job["number"].to_int(-1), job["importance"].to_int(1),
1830  job["total"].to_bool(false),
1831  job["blocker"].to_bool(true),
1832  job["pattern"].to_bool(true)
1833  ));
1834  jobs.push_back(job_ptr);
1835 }
1836 
1839  utils::split(lim["type"]),
1840  lim["id"],
1841  lim["max"].to_int(0)
1842  ));
1843  limits.push_back(lim_ptr);
1844 }
1845 
1846 } // namespace default_recruitment
1847 } // namespace ai
virtual side_number get_side() const
Get the side number.
Definition: contexts.hpp:480
std::string to_string() const
Definition: recruitment.hpp:89
child_itors child_range(const std::string &key)
Definition: config.cpp:613
const double * get_cached_combat_value(const std::string &a, const std::string &b, double a_defense, double b_defense)
For Combat Analysis.
int total_movement() const
Definition: unit.hpp:218
::tod_manager * tod_manager
Definition: resources.cpp:31
std::vector< unit_iterator > find_leaders(int side)
Definition: map.cpp:319
unit_iterator end()
Definition: map.hpp:311
GLuint counter
Definition: glew.h:2584
const map_location & get_location() const
Definition: unit.hpp:286
std::map< size_t, int > cheapest_unit_costs_
std::vector< boost::shared_ptr< recruit_job > > jobs_
int max_hitpoints() const
Definition: unit.hpp:169
bool recruit_matches_job(const std::string &recruit, const config *job) const
For Configuration / Aspect "recruitment-instructions" Checks if a given recruit-type is specified in ...
int village_support
Definition: game_config.cpp:39
void update_average_local_cost()
For Map Analysis.
Definition: unit.hpp:95
boost::shared_ptr< config > value_
Definition: aspect.hpp:189
bool remove_job_if_no_blocker(config *job)
For Configuration / Aspect "recruitment-instructions".
map_location find_vacant_castle(const unit &leader)
Wrapper for find_vacant_tile() when looking for a vacant castle tile near a leader.
Definition: pathfind.cpp:121
std::set< std::string > advancement_tree() const
Get the advancement tree Build a set of unit type's id of this unit type's advancement tree...
Definition: types.cpp:642
bool better_result(const attack_simulation *other, bool for_defender)
virtual recruit_result_ptr check_recruit_action(const std::string &unit_name, const map_location &where=map_location::null_location(), const map_location &from=map_location::null_location())
Definition: contexts.hpp:572
virtual void execute()
Execute the candidate action.
#define ERR_AI_RECRUITMENT
Definition: recruitment.cpp:52
double get_avg_hp_of_combatant(bool attacker) const
Various functions that implement attacks and attack calculations.
const std::set< std::string > & recruits() const
Definition: team.hpp:228
#define LOG_AI_RECRUITMENT
Definition: recruitment.cpp:51
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
static bool better_combat(const combatant &us_a, const combatant &them_a, const combatant &us_b, const combatant &them_b, double harm_weight)
Definition: attack.cpp:442
virtual const config get_recruitment_instructions() const
Definition: contexts.hpp:824
const std::string get_random_pattern_type_if_exists(const data &leader_data, const config *job) const
For Configuration / Aspect "recruitment-instructions" If the flag pattern is set, this method returns...
GLuint GLuint GLsizei GLenum type
Definition: glew.h:1221
bool limit_ok(const std::string &recruit) const
For Configuration / Aspect "recruitment-instructions" Checks if a recruit-type can be recruited accor...
int hitpoints() const
Definition: unit.hpp:168
virtual const std::vector< std::string > get_recruitment_more() const
Definition: contexts.hpp:830
logger & info()
Definition: log.cpp:91
void handle_recruitment_more(std::vector< data > *leader_data) const
For Aspect "recruitment_more".
Managing the AI-Game interaction - AI actions and their results.
game_display * screen
Definition: resources.cpp:27
void get_tiles_in_radius(const map_location &center, const int radius, std::vector< map_location > &result)
Function that will add to result all locations within radius tiles of center (excluding center itself...
Definition: pathutils.cpp:56
const movetype & movement_type() const
Definition: types.hpp:150
property_handler_map & property_handlers()
Definition: component.cpp:133
double get_score() const
Get the usual score of the candidate action without re-evaluation.
Definition: rca.cpp:72
bool is_enemy(int n) const
Definition: team.hpp:247
std::map< map_location, double > average_local_cost_
void update_important_hexes()
For Map Analysis.
std::vector< boost::shared_ptr< recruit_limit > > limits_
data * get_best_leader_from_ratio_scores(std::vector< data > &leader_data, const config *job) const
A helper function for execute().
unit_type_data unit_types
Definition: types.cpp:1314
void do_randomness(std::vector< data > *leader_data) const
Will add a random value between 0 and "recruitment_randomness" to all recruits.
void update_scouts_wanted()
This function will use the aspect villages_per_scout to decide how many scouts we want to recruit...
int side() const
Definition: unit.hpp:201
int get_cheapest_unit_cost_for_leader(const unit_map::const_iterator &leader)
Called at the beginning and whenever the recruitment list changes.
GLint GLint GLint GLint GLint GLint y
Definition: glew.h:1220
static void register_vector_property(std::map< std::string, property_handler_ptr > &property_handlers, const std::string &property, std::vector< boost::shared_ptr< X > > &values, std::function< void(std::vector< boost::shared_ptr< X > > &, const config &)> construction_factory)
void clear()
Definition: config.cpp:1055
const std::string number
template to number regex
void add_unit(const unit &u, bool use_max_moves=true)
Adds a units cost map to cost_map (increments the elements in cost_map)
Definition: pathfind.cpp:879
virtual int get_villages_per_scout() const
Definition: contexts.hpp:884
const std::string get_best_recruit_from_scores(const data &leader_data, const config *job)
A helper function for execute().
-file sdl_utils.hpp
GLint GLenum GLsizei GLint GLsizei const GLvoid * data
Definition: glew.h:1347
int level() const
Definition: types.hpp:126
void clear_children(const std::string &key)
Definition: config.cpp:820
double get_estimated_unit_gain() const
For Aspect "recruitment_save_gold".
recruit_situation_change_observer recruit_situation_change_observer_
int round_double(double d)
Definition: util.hpp:67
GLsizei GLenum GLenum * types
Definition: glew.h:3155
double get_average_cost_at(int x, int y) const
Accessor for the costs.
Definition: pathfind.cpp:953
std::set< map_location > important_hexes_
double compare_unit_types(const std::string &a, const std::string &b)
For Combat Analysis.
const battle_context_unit_stats attacker_stats
const terrain_label * set_label(const map_location &loc, const t_string &text, const int creator=-1, const std::string &team="", const SDL_Color color=font::NORMAL_COLOR, const bool visible_in_fog=true, const bool visible_in_shroud=false, const bool immutable=false, const std::string &category="", const t_string &tooltip="")
Definition: label.cpp:145
virtual double power_projection(const map_location &loc, const move_map &dstsrc) const
Function which finds how much 'power' a side can attack a certain location with.
Definition: contexts.hpp:806
GLdouble GLdouble GLdouble b
Definition: glew.h:6966
double get_estimated_village_gain() const
For Aspect "recruitment_save_gold".
int side_upkeep(int side_num) const
bool recruit_matches_type(const std::string &recruit, const std::string &type) const
For Configuration / Aspect "recruitment-instructions" Checks if a given recruit-type matches one atom...
Object which defines a time of day with associated bonuses, image, sounds etc.
Definition: time_of_day.hpp:48
bool has_child(const std::string &key) const
Determine whether a config has a child or not.
Definition: config.cpp:651
const std::string & type_id() const
The id of the type of the unit.
Definition: unit.hpp:142
double get_unit_ratio() const
For Aspect "recruitment_save_gold".
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:50
A small explanation about what's going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:57
static void remove_gamestate_observer(events::observer *event_observer)
Removes an observer of game events except ai_user_interact event and ai_sync_network event...
Definition: manager.cpp:378
bool incapacitated() const
Definition: unit.hpp:215
int recall_cost() const
Definition: team.hpp:198
GLuint GLuint end
Definition: glew.h:1221
double get_average_defense(const std::string &unit_type) const
For Map Analysis.
std::vector< team > * teams
Definition: resources.cpp:29
void update_average_lawful_bonus()
Calculates a average lawful bonus, so Combat Analysis will work better in caves and custom time of da...
GLint limit
Definition: glew.h:10112
filter_context * filter_con
Definition: resources.cpp:23
void integrate_recruitment_pattern_in_recruitment_instructions()
For Configuration / Aspect "recruitment_pattern" Converts the (old) recruitment_pattern into a recrui...
GLsizei const GLfloat * value
Definition: glew.h:1817
int base_income() const
Definition: team.hpp:196
int w() const
Effective map width.
Definition: map.hpp:105
game_board * gameboard
Definition: resources.cpp:20
config * get_most_important_job()
For Configuration / Aspect "recruitment-instructions" We call a [recruit] tag a "job".
GLboolean GLboolean GLboolean GLboolean a
Definition: glew.h:7319
virtual config to_config() const
serialize
Definition: rca.cpp:88
Encapsulates the map of the game.
Definition: map.hpp:37
bool can_recruit() const
Definition: unit.hpp:207
config & add_child(const std::string &key)
Definition: config.cpp:743
void fight(combatant &opponent, bool levelup_considered=true)
Simulate a fight! Can be called multiple times for cumulative calculations.
bool is_enemy_in_radius(const map_location &loc, int radius) const
Helper function.
Managing the AIs lifecycle - headers.
void create_limit(std::vector< boost::shared_ptr< recruit_limit > > &limits, const config &lim)
Structure describing the statistics of a unit involved in the battle.
Definition: attack.hpp:49
Recruitment Engine by flix See http://wiki.wesnoth.org/AI_Recruitment.
static const map_location & null_location()
Definition: location.hpp:195
void do_combat_analysis(std::vector< data > *leader_data)
Combat Analysis.
const std::set< map_location > & villages() const
Definition: team.hpp:189
static const ::config * terrain
The terrain used to create the cache.
Definition: minimap.cpp:135
Templates and utility-routines for strings and numbers.
terrain_costs & get_movement()
Definition: movetype.hpp:181
GLuint GLuint GLsizei count
Definition: glew.h:1221
virtual const move_map & get_enemy_dstsrc() const
Definition: contexts.hpp:709
bool recruit_matches_types(const std::string &recruit, const std::vector< std::string > &types) const
For Configuration / Aspect "recruitment-instructions" Checks if a given recruit-type matches one of t...
config to_config() const
serialize
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:47
action_result_ptr execute_recall(const std::string &id, data &leader_data)
A helper function for execute().
int cost() const
Definition: types.hpp:134
virtual double get_recruitment_diversity() const
Definition: contexts.hpp:818
Encapsulates the map of the game.
Definition: location.hpp:38
virtual const config get_recruitment_save_gold() const
Definition: contexts.hpp:848
All combat-related info.
const std::string & usage() const
Definition: types.hpp:137
std::stringstream & wml_error()
Use this logger to send errors due to deprecated WML.
Definition: log.cpp:262
int h() const
Effective map height.
Definition: map.hpp:108
virtual recall_result_ptr check_recall_action(const std::string &id, const map_location &where=map_location::null_location(), const map_location &from=map_location::null_location())
Definition: contexts.hpp:565
GLubyte * pattern
Definition: glew.h:4192
int village_income
Definition: game_config.cpp:38
void compare_cost_maps_and_update_important_hexes(const pathfind::full_cost_map &my_cost_map, const pathfind::full_cost_map &enemy_cost_map)
For Map Analysis Computes from our cost map and the combined cost map of all enemies the important he...
static tcache cache
Definition: minimap.cpp:139
int turn() const
Structure which uses find_routes() to build a cost map This maps each hex to a the movements a unit w...
Definition: pathfind.hpp:267
size_t i
Definition: function.cpp:1057
action_result_ptr execute_recruit(const std::string &type, data &leader_data)
A helper function for execute().
static void add_recruit_list_changed_observer(events::observer *event_observer)
Adds an observer of 'ai_recruit_list_changed' event.
Definition: manager.cpp:402
GLint GLint GLint GLint GLint x
Definition: glew.h:1220
int side() const
Definition: team.hpp:193
double get_estimated_income(int turns) const
For Aspect "recruitment_save_gold".
int cost(const t_translation::t_terrain &terrain, bool slowed=false) const
Returns the cost associated with the given terrain.
Definition: movetype.hpp:109
virtual const team & current_team() const
Definition: contexts.hpp:539
int hitpoints() const
Definition: types.hpp:123
bool valid_
Definition: aspect.hpp:100
virtual const gamemap & map() const
Definition: game_board.hpp:98
attack_simulation(const unit_type *attacker, const unit_type *defender, double attacker_defense, double defender_defense, const attack_type *att_weapon, const attack_type *def_weapon, int average_lawful_bonus)
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:467
GLsizeiptr size
Definition: glew.h:1649
void create_job(std::vector< boost::shared_ptr< recruit_job > > &jobs, const config &job)
bool find(E event, F functor)
Tests whether an event handler is available.
cl_event event
Definition: glew.h:3070
recruitment(rca_context &context, const config &cfg)
const std::string * get_appropriate_recall(const std::string &type, const data &leader_data) const
A helper function for execute().
static void add_gamestate_observer(events::observer *event_observer)
Adds observer of game events except ai_user_interact event and ai_sync_network event.
Definition: manager.cpp:371
config & child(const std::string &key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:658
int cost() const
Definition: unit.hpp:284
std::vector< attack_type > attacks() const
Definition: types.cpp:484
void simulate_attack(const unit_type *const attacker, const unit_type *const defender, double attacker_defense, double defender_defense, double *damage_to_attacker, double *damage_to_defender) const
For Combat Analysis.
virtual int get_recruitment_randomness() const
Definition: contexts.hpp:842
A variable-expanding proxy for the config class.
Definition: variable.hpp:36
static lg::log_domain log_ai_recruitment("ai/recruitment")
Standard logging facilities (interface).
bool recall_unit(const std::string &id, team &current_team, const map_location &loc, const map_location &from, bool show, bool use_undo)
Recalls the unit with the indicated ID for the provided team.
Definition: create.cpp:731
recruitment_aspect(readonly_context &context, const config &cfg, const std::string &id)
static const double BAD_SCORE
Definition: rca.hpp:38
Container associating units to locations.
Definition: map.hpp:90
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp:1155
double average_hp(unsigned int healing=0) const
What's the average hp (weighted average of hp_dist).
std::vector< std::string > split(std::string const &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
map_labels & labels()
Definition: display.cpp:2773
score_map get_normalized_scores() const
Definition: recruitment.hpp:78
unit_iterator find(size_t id)
Definition: map.cpp:285
int get_cost_at(int x, int y) const
Accessor for the costs.
Definition: pathfind.cpp:942
void clear_all()
Definition: label.cpp:246
unit_map::const_iterator leader
Definition: recruitment.hpp:57
std::string::const_iterator iterator
Definition: tokenizer.hpp:21
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:83
virtual const std::vector< std::string > get_recruitment_pattern() const
Definition: contexts.hpp:836
virtual double evaluate()
Evaluate the candidate action, resetting the internal state of the action.
GLdouble s
Definition: glew.h:1358
void update_state()
For Aspect "recruitment_save_gold".
const pathfind::full_cost_map get_cost_map_of_side(int side) const
For Map Analysis.
const std::vector< map_location > & villages() const
Return a list of the locations of villages on the map.
Definition: map.hpp:163
const GLuint * ids
Definition: glew.h:1652
This module contains various pathfinding functions and utilities.
void show_important_hexes() const
For Map Analysis.
const battle_context_unit_stats defender_stats
GLsizei const GLcharARB ** string
Definition: glew.h:4503
void do_similarity_penalty(std::vector< data > *leader_data) const
Will give a penalty to similar units.
static void remove_recruit_list_changed_observer(events::observer *event_observer)
Deletes an observer of 'ai_recruit_list_changed' event.
Definition: manager.cpp:414
unit_map * units
Definition: resources.cpp:35
std::set< std::string > recruits
Definition: recruitment.hpp:58
const std::string & id() const
The id for this unit_type.
Definition: types.hpp:115
int defense_modifier(const t_translation::t_terrain &terrain) const
Returns the defensive value of the indicated terrain.
Definition: movetype.hpp:209
double poisoned
Resulting chance we are poisoned.
candidate action framework
bool leader_matches_job(const data &leader_data, const config *job) const
For Configuration / Aspect "recruitment-instructions" Checks if a given leader is specified in the "l...